/*
 * AlphaColor.java
 *
 * Created on November 8, 2006, 5:17 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package edu.umn.cs5115.scheduler.framework.gui;

import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;

import edu.umn.cs5115.scheduler.framework.DocumentController;

/**
 * Represents an SWT color with alpha.
 * @author grant
 */
public class AlphaColor
{
    /** Black, opaque color. */
    public final static AlphaColor BLACK = new AlphaColor(0,0,0);
    /** White, opaque color. */
    public final static AlphaColor WHITE  = new AlphaColor(255,255,255);
    /** Pure, opaque RGB Red. */
    public final static AlphaColor RED = new AlphaColor(255, 0, 0);
    /** Pure, opaque, RGB Green. */
    public final static AlphaColor GREEN = new AlphaColor(0, 255, 0);
    /** Pure, opaque, RGB Blue. */
    public final static AlphaColor BLUE = new AlphaColor(0, 0, 255);
    /** Pure, opaque, RGB Yellow. */
    public final static AlphaColor YELLOW = new AlphaColor(255, 255, 0);
    /** Pure, opaque, RGB Cyan. */
    public final static AlphaColor CYAN = new AlphaColor(0, 255, 255);
    /** Pure, opaque, RGB Purple. */
    public final static AlphaColor PURPLE = new AlphaColor(255, 0, 255);
    
    /** underlying color. */
    private Color swtColor;
    
    // color channel information, so that we can produce the color as needed.
    /** Red channel, in the range [0, 255]. */
    private int red;
    /** Green channel, in the range [0, 255]. */
    private int green;
    /** Blue channel, in the range [0, 255]. */
    private int blue;
    /** Alpha channel, in the range [0, 255].  Since SWT colors don't include
     * an alpha channel (which is this class's reason for existence), this class
     * needs to maintain this to set it each time the color is used. */
    private int alpha;
    
    /** 
     * Creates a new, fully opaque instance of AlphaColor.  Values outside the 
     * range [0, 255] are set to the nearest value.
     * @param red The red channel for this color [0, 255]
     * @param green The green channel for this color [0, 255]
     * @param blue The blue channel for this color [0, 255]
     */
    public AlphaColor(int red, int green, int blue)
    {
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.alpha = 255;
        clampValues();
    }
    
    /** 
     * Creates a new instance of AlphaColor.  Values outside the range [0, 255]
     * are set to the nearest value.
     * @param red The red channel for this color [0, 255]
     * @param green The green channel for this color [0, 255]
     * @param blue The blue channel for this color [0, 255]
     * @param alpha The alppha channel for this color [0, 255]
     */
    public AlphaColor(int red, int green, int blue, int alpha)
    {
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.alpha = alpha;
        clampValues();
    }
    
    /** 
     * Creates a new instance of AlphaColor.
     * @param red The red channel for this color (float betweeen 0.0 and 1.0)
     * @param green The green channel for this color (float betweeen 0.0 and 1.0)
     * @param blue The blue channel for this color (float betweeen 0.0 and 1.0)
     * @param alpha The alpha channel for this color (float betweeen 0.0 and 1.0)
     */
    public AlphaColor(float red, float green, float blue, float alpha)
    {
        this.red = (int)(red*255);
        this.green = (int)(green*255);
        this.blue = (int)(blue*255);
        this.alpha = (int)(alpha*255);
        clampValues();
    }
    
    /** 
     * Creates a new, fully opaque instance of AlphaColor.
     * @param red The red channel for this color (float betweeen 0.0 and 1.0)
     * @param green The green channel for this color (float betweeen 0.0 and 1.0)
     * @param blue The blue channel for this color (float betweeen 0.0 and 1.0)
     */
    public AlphaColor(float red, float green, float blue)
    {
        this.red = (int)(red*255);
        this.green = (int)(green*255);
        this.blue = (int)(blue*255);
        this.alpha = 255;
        clampValues();
    }
    
    /**
     * Mixes this color with the specified fraction of another color, and 
     * returns a new color with the mixture.
     * @param fraction The amount of otherColor to blend in.  Expressed as a
     * decimal between 0.0 and 1.0
     * @param otherColor The color to blend with this one.
     */
    public AlphaColor blendWithColor(float fraction, AlphaColor otherColor)
    {
        fraction = clampValue(fraction, 0.0f, 1.0f);
        float myFraction = 1.0f - fraction;
        float red = getRedFloat() * myFraction + otherColor.getRedFloat() * fraction;
        float green = getGreenFloat() * myFraction + otherColor.getGreenFloat() * fraction;
        float blue = getBlueFloat() * myFraction + otherColor.getBlueFloat() * fraction;
        float alpha = getAlphaFloat() * myFraction + otherColor.getAlphaFloat() * fraction;
        return new AlphaColor(red, green, blue, alpha);
    }
    
    /**
     * Tests to see if value is inside the range [lower, upper].  If it is,
     * it returns value.  If not, it sets it to the closest one and returns
     * that instead.
     * @param value The value to test.
     * @param lower The lower bound to test against.
     * @param upper The upper bound to test against.
     * @return value, if it is between lower and upper, lower, if value is less
     * than lower, or upper if value is greater than upper.
     */
    private int clampValue(int value, int lower, int upper)
    {
        if (value < lower)
            value = lower;
        else if (value > upper)
            value = upper;
        return value;
    }
    /**
     * Clamp floats.  Tests to see if value is inside the range [lower, upper].  
     * If it is, it returns value.  If not, it sets it to the closest one and 
     * returns that instead.
     * @param value The value to test.
     * @param lower The lower bound to test against.
     * @param upper The upper bound to test against.
     * @return value, if it is between lower and upper, lower, if value is less
     * than lower, or upper if value is greater than upper.
     */
    private float clampValue(float value, float lower, float upper)
    {
        if (value < lower)
            value = lower;
        else if (value > upper)
            value = upper;
        return value;
    }
    
    /**
     * Makes sure all of our values are in range, and sets them to the closest
     * in range value if they are not.
     */
    private void clampValues()
    {
        red = clampValue(red, 0, 255);
        green = clampValue(green, 0, 255);
        blue = clampValue(blue, 0, 255);
        alpha = clampValue(alpha, 0, 255);
    }
    
    /**
     * Returns the SWT color to use to draw this color.  If it doesn't exists 
     * yet, it is created first.
     * @return The SWT color representing the red, green, and blue components
     * of this color.
     */
    private Color getColor()
    {
        if (swtColor == null)
            swtColor = new Color(DocumentController.getSharedDocumentController().getDisplay(), red, green, blue);
        return swtColor;
    }
    
    /**
     * Sets the color for foreground use with a given GC.
     * @param gc The graphics context to configure to draw in this color.
     */
    public void setForeground(GC gc)
    {
        gc.setForeground(getColor());
        gc.setAlpha(alpha);
    }
    
    /**
     * Sets the color for background use with a given GC.
     * @param gc The graphics context to configure to draw in this color.
     */
    public void setBackground(GC gc)
    {
        gc.setBackground(getColor());
        gc.setAlpha(alpha);
    }
    /** 
     * Frees the underlying SWT Color class, which also in turn frees OS 
     * resources.
     * This method gets automatically called once the color is thrown away by
     * Java's garbage collection system, but you can call it earlier to free
     * the resources sooner.
     */
    public void dispose()
    {
        if (swtColor != null)
        {
            swtColor.dispose();
            swtColor = null;
        }
    }
    
    /**
     * Overridden to call dispose.
     * @throws Throwable if the super class's implementation throws this when it
     * is invoked.
     */
    protected void finalize() throws Throwable
    {
        dispose();
        super.finalize();
    }

    /**
     * Get the red component of this color.
     * @return The red component of the color, a value between 0 and 255.
     */
    public int getRed()
    {
        return red;
    }

    /**
     * Get the green component of this color.
     * @return The green component of this color, an integer value between 0 and 255.
     */
    public int getGreen()
    {
        return green;
    }
    
    /**
     * Get the blue component of this color.
     * @return The blue component of this color, an integer value between 0 and 255.
     */
    public int getBlue()
    {
        return blue;
    }
    
    /**
     * Get the alpha (transparency) component of this color.
     * @return The alpha component of this color, an integer value between 0 and 255.
     * 0 is transparent and 255 is opaque.
     */
    public int getAlpha()
    {
        return alpha;
    }

    /**
     * Get the red component of this color as a floating point number.
     * @return The red component of the color, a floating point value between 0 and 1.0.
     */
    public float getRedFloat()
    {
        return red/255.0f;
    }

    /**
     * Get the green component of this color as a floating point number.
     * @return The green component of the color, a floating point value between 0 and 1.0.
     */
    public float getGreenFloat()
    {
        return green/255.0f;
    }
    
    /**
     * Get the blue component of this color as a floating point number.
     * @return The blue component of the color, a floating point value between 0 and 1.0.
     */
    public float getBlueFloat()
    {
        return blue/255.0f;
    }
    
    /**
     * Get the alpha (transparency) component of this color as a floating point number.
     * @return The alpha component of the color, a floating point value between 0 and 1.0.
     * 0.0 is transparent and 1.0 is opaque.
     */
    public float getAlphaFloat()
    {
        return alpha/255.0f;
    }
    
    /**
     * Test color equality.
     * @param o Another object.
     * @return True if the other object is an AlphaColor instance with identical
     * values for each channel, false otherwise.
     */
    public boolean equals(Object o)
    {
        if (o instanceof AlphaColor)
        {
            AlphaColor other = (AlphaColor)o;
            if (other.getRed() == red && other.getGreen() == green && other.getBlue() == blue)
                return true;
        }
        return false;
    }
    
    /**
     * Returns a hash code for this object.  By the general contract of 
     * hashcode, if equals returns true for any two objects, the hash code will
     * also be identical.
     * @return A hash of this color's components.
     */
    public int hashCode()
    {
        return (alpha<<24 | red << 16 | green << 8 | blue);
    }
}
